package mini.oo;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import mini.Env;
import mini.ParamList;
import mini.Proc;
import mini.StatementList;
import mini.expr.Element;
import mini.expr.Expr;
import mini.statement.AssignStatement;
import mini.statement.Statement;
import mini.util.MiniUtil;

public class MiniClassDefinition extends Statement {

    private ParamList parameterlist;
    private StatementList stmtlist;
    private String name;
    private String superClass;
    
	public MiniClassDefinition(String str, ParamList pl, StatementList sl) {
		name = str;
        parameterlist = pl;
        stmtlist = sl;
        superClass = null;
    }

	public MiniClassDefinition(String str, ParamList pl, StatementList sl, String superClass) {
		name = str;
        parameterlist = pl;
        stmtlist = sl;
        this.superClass = superClass;
    }

	public void eval(List<Env> listOfEnvs, LinkedList var) {
		if( superClass != null ) {
			establishSuperclass(listOfEnvs);
		}
        if( listOfEnvs != null && listOfEnvs.size() > 0 ) {
        	Env env = listOfEnvs.get(listOfEnvs.size()-1);
        	env.addToClasstable(name, this);
        } else {
        	System.err.println( "Internal error in Class Definition of " + name + "=" + this );
        }
    }

	/**
	 * We establish the superclass by just copying over its statements.
	 * I'm not sure if this actually is a problematic way to handle
	 * it or not.
	 * 
	 * @param listOfEnvs
	 */
	private void establishSuperclass(List<Env> listOfEnvs) {

		if( superClass != null ) {
			
			// Is it ourselves?
			if( superClass.equals(name) ) {
				System.err.println( "You can't have a class that extends itself. It would recurse endlessly and destroy life as we know it!" );
				System.err.println( "Aborting" );
				System.exit(1);
			}
			
			// Does it exist?
			MiniClassDefinition superClassDef = MiniUtil.lookupClass(listOfEnvs, superClass);
			if( superClassDef == null ) {
				System.err.println( "Could not find te superclass " + superClass + "; it must be defined before the class it extends in the file." );
				System.err.println( "Aborting" );
				System.exit(1);
			}
			
			// Get a list of statements in the super class
			StatementList superStatementList = superClassDef.getStatementList();
			List<Statement> superStatements = new ArrayList<Statement>();
			if( superStatementList != null && superStatementList.getStatements() != null ) {
				for( Statement superStatement : superStatementList.getStatements() ) {
					superStatements.add(superStatement);
				}
			}
			
			// Add them BEFORE any statements in our list
			if( stmtlist != null && stmtlist.getStatements() != null ) {
				for( int i = superStatements.size()-1; i >= 0; i-- ) {
					stmtlist.getStatements().addFirst( superStatements.get(i) );
				}
			}
		}
	}

	public String getName() {
		return name;
	}

	/**
	 * Get a list of parameters. Guaranteed non-null
	 * @return Non-null list of parameters
	 */
	public List<String> getParameters() {
		
		List<String> parameters = new ArrayList<String>();
		
		if( parameterlist != null && parameterlist.getParamList() != null ) {
			parameters = parameterlist.getParamList();
		}
		
		return parameters;
		
	}
	
	/**
	 * There's a bunch of statements in the class definition. Some of them
	 * are assignments. Of those, some are assigning to procedures, which
	 * we ignore (as those are the methods). Of the others, we create an
	 * environment matching the identifier of the assignment to the evaluated
	 * value of the expression that corresponds to it
	 * @param An env of the parameters passed in, just so we can evaluate
	 * expressions
	 * 
	 * @return An environment of the class variables
	 */
	public Env initializeEnvironment( Env paramEnv ) {
		
		// We need an array of input environments, even if it's just one
		List<Env> paramEnvs = new ArrayList<Env>();
		paramEnvs.add(paramEnv);
		
		Env env = new Env();
		LinkedList var = new LinkedList();	// We really don't care, but not sure if it can be null
		
		// We just want to process assignment statements
    	if( stmtlist != null && stmtlist.getStatements() != null ) {
    		for( Statement stmt : stmtlist.getStatements() ) {
    			if( stmt instanceof AssignStatement ) {
    				AssignStatement assignStmt = (AssignStatement)stmt;
    				// Found one!
    				if( assignStmt.getName() != null && assignStmt.getExpr() != null ) {
    					// We found something, but only process if not a procedure
    					Expr expr = assignStmt.getExpr();
    					if( !(expr instanceof Proc) ) {
    						// okay, so we ARE interested in it
    						String name = assignStmt.getName();
    						Element value = expr.eval(paramEnvs, var);
    						env.addToNametable(name, value);
    					}
    				}
    			}
    		}
    	}
    	
		return env;
	}

	/**
	 * Given the name of a method, return an expression that represents it
	 * 
	 * @param mName
	 * @return
	 */
	public Proc getMethod(String mName) {

		Proc proc = null;
		
    	if( stmtlist != null && stmtlist.getStatements() != null ) {
    		for( Statement stmt : stmtlist.getStatements() ) {
    			if( stmt instanceof AssignStatement ) {
    				AssignStatement assignStmt = (AssignStatement)stmt;
    				if( assignStmt.getName() != null && assignStmt.getName().equals(mName) ) {
    					// We found something with the name -- but is it a procedure?
    					Expr expr = assignStmt.getExpr();
    					if( expr instanceof Proc ) {
    						proc = (Proc)expr;
    					}
    				}
    			}
    		}
    	}
    	
    	return proc;
	}
	
	protected StatementList getStatementList() {
		return stmtlist;
	}
}
